﻿using System;
using System.Linq;
using System.Reflection;

namespace ExcellentCRM.DataService.Exts
{
    public static class ObjectExtensions
    {
        /// <summary>
        /// 将源对象中与目标对象中同名同类属性合并(拷贝源对象中的到目标对象中)
        /// </summary>
        /// <example>将A对象中的值拷贝到B对象 : B.MergeFrom(A);</example>
        /// <typeparam name="T">源类型</typeparam>
        /// <param name="destination">目标对象</param>
        /// <param name="source">源对象</param>
        /// <param name="excludeFields">排除字段</param>
        public static void MergeFrom<T>(this object destination, T source, params string[] excludeFields)
        {
            Type destinationType = destination.GetType();
            PropertyInfo[] propertyInfos = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (var propertyInfo in propertyInfos)
            {
                PropertyInfo dstPropInfo = destinationType.GetProperty(propertyInfo.Name,
                                                                                   BindingFlags.Public |
                                                                                   BindingFlags.Instance);
                if (dstPropInfo == null) continue;
                Type propType = propertyInfo.PropertyType,
                    dstPropType = dstPropInfo.PropertyType;
                bool srcPropIsNullable = propType.IsGenericType &&
                                         propType.GetGenericTypeDefinition() == typeof(Nullable<>);

                bool dstPropIsNotNullable = srcPropIsNullable &&
                                                  dstPropType == propType.GenericTypeArguments[0];

                bool dstPropIsNullable = dstPropType.IsGenericType &&
                                         dstPropType.GetGenericTypeDefinition() == typeof(Nullable<>);

                bool srcPropIsNotNullable = dstPropIsNullable &&
                                            propType == dstPropType.GenericTypeArguments[0];

                bool ignorePropTypeNotEquals = (srcPropIsNullable && dstPropIsNotNullable) ||
                                               (dstPropIsNullable && srcPropIsNotNullable);
                if (!dstPropInfo.CanWrite || !propertyInfo.CanRead ||
                    (dstPropType != propType && !ignorePropTypeNotEquals) ||
                    (excludeFields != null && excludeFields.Contains(propertyInfo.Name))) continue;
                object srcValue = propertyInfo.GetValue(source, null);

                //当原属性为Nullable<int>，target属性为int（或相反），只在srcValue不为null才设置target值
                if (!ignorePropTypeNotEquals || srcValue != null)
                    dstPropInfo.SetValue(destination, srcValue, null);
            }
        }
    }
}